package
{
	import com.adobe.utils.*;
	
	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.*;
	import flash.display3D.textures.Texture;
	import flash.events.*;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.ui.Keyboard;
	
	[SWF(width="800", frameRate="60", backgroundColor="#FFFFFF")]
	/**
	 * 
	 * @author Marco Scabia
	 * 
	 */
	public class WebMoleHillTest extends Sprite
	{
		public const viewWidth:Number = 800;
		public const viewHeight:Number = 600;
		
		[Embed( source = "/assets/image/RockSmooth.jpg" )]
		protected const TextureBitmap:Class;
		
		protected var context3D:Context3D;
		protected var vertexbuffer:VertexBuffer3D;
		protected var indexBuffer:IndexBuffer3D; 
		protected var program:Program3D;
		protected var texture:Texture;
		protected var projectionTransform:PerspectiveMatrix3D;
		protected var cameraWorldTransform:Matrix3D;
		protected var viewTransform:Matrix3D;
		protected var cameraLinearVelocity:Vector3D;
		protected var cameraRotationVelocity:Number;
		protected var cameraRotationAcceleration:Number;
		protected var cameraLinearXAcceleration:Number;
		protected var cameraLinearZAcceleration:Number;
		
		protected const MAX_FORWARD_VELOCITY:Number = 0.05;
		protected const MAX_ROTATION_VELOCITY:Number = 0.5;
		protected const LINEAR_ACCELERATION:Number = 0.01;
		protected const ROTATION_ACCELERATION:Number = 0.01;
		protected const DAMPING:Number = 1.09;
		
		// controls
		private const MOVING_SPEED:Number = .05;
		private var _currentKeyCodes:Vector.<uint> = new Vector.<uint>();
		
		private var _mouseMoveX:Number = -1;
		private var _mouseMoveY:Number = -1;
		
		private var _mouseDeltaX:Number = 0;
		private var _mouseDeltaY:Number = 0;
		
		[Embed(source="/assets/image/default_gun.png")]
		private var DefaultGun:Class;
		[Embed(source="/assets/image/sniper_gun.png")]
		private var SniperGun:Class;
		
		private var _player:Bitmap;
		
		public function WebMoleHillTest()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill );
			stage.stage3Ds[0].requestContext3D();
			
			_player = new DefaultGun();
			_player.scaleX = .3;
			_player.scaleY = .3;
			_player.x = (this.stage.stageWidth - _player.width) * .5;
			_player.y = stage.y + viewHeight - _player.height;
			addChild(_player);
			
			var gameInfoText:TextField = new TextField();
			gameInfoText.autoSize = TextFieldAutoSize.LEFT;
			gameInfoText.y = viewHeight;
			addChild(gameInfoText);
			
			gameInfoText.text = "이동 : W, S, D, F\n무기 변경 : 1(라플), 2(스나)";
		}
		
		private function initMolehill( e:Event ):void
		{
			if( Stage3D(e.target ).context3D != null )
			{
				context3D = Stage3D( e.target ).context3D;
				trace( "3D driver: " + context3D.driverInfo );
				setupScene();
			}
			else throw( new Error( "Did not get render context for 3D." ));
		}
		
		private function setupScene():void
		{
			context3D.configureBackBuffer(viewWidth, viewHeight, 1, true);
			
			const dataPerVertex:int = 5;
			var vertexData:Vector.<Number> = Vector.<Number>(
				[
					// left cube Front face
					-5.0, -5.0,  5.0, 0.0, 0.0,
					5.0, -5.0,  5.0, 1.0, 0.0,
					5.0,  5.0,  5.0, 1.0, 1.0,
					-5.0,  5.0,  5.0, 0.0, 1.0,
					
					// left cube Back face
					-5.0, -5.0, -5.0, 1.0, 0.0,
					-5.0,  5.0, -5.0, 1.0, 1.0,
					5.0,  5.0, -5.0, 0.0, 1.0,
					5.0, -5.0, -5.0, 0.0, 0.0,
					
					// left cube Top face
					-5.0,  5.0, -5.0, 0.0, 1.0,
					-5.0,  5.0,  5.0, 0.0, 0.0,
					5.0,  5.0,  5.0, 1.0, 0.0,
					5.0,  5.0, -5.0, 1.0, 1.0,
					
					// left cube Bottom face
					-5.0, -5.0, -5.0, 1.0, 1.0,
					5.0, -5.0, -5.0, 0.0, 1.0,
					5.0, -5.0,  5.0, 0.0, 0.0,
					-5.0, -5.0,  5.0, 1.0, 0.0,
					
					// left cube Right-front
					5.0, -5.0, 1.0, 1.0, 0.0,
					5.0,  5.0, 1.0, 1.0, 1.0,
					5.0,  5.0,  5.0, 0.0, 1.0,
					5.0, -5.0,  5.0, 0.0, 0.0,
					
					// left cube Right-back
					5.0, -5.0, -5.0, 1.0, 0.0,
					5.0,  5.0, -5.0, 1.0, 1.0,
					5.0,  5.0,  -1.0, 0.0, 1.0,
					5.0, -5.0,  -1.0, 0.0, 0.0,
					
					// left cube Left face
					-5.0, -5.0, -5.0, 0.0, 0.0,
					-5.0, -5.0,  5.0, 1.0, 0.0,
					-5.0,  5.0,  5.0, 1.0, 1.0,
					-5.0,  5.0, -5.0, 0.0, 1.0,
					
					//connect front face
					5.0, -5.0,  1.0, 0.0, 0.0,
					20.0, -5.0,  1.0, 1.0, 0.0,
					20.0,  5.0,  1.0, 1.0, 1.0,
					5.0,  5.0,  1.0, 0.0, 1.0,
					
					//connect back face
					5.0, -5.0, -1.0, 1.0, 0.0,
					5.0,  5.0, -1.0, 1.0, 1.0,
					20.0,  5.0, -1.0, 0.0, 1.0,
					20.0, -5.0, -1.0, 0.0, 0.0,
					
					//connect top face
					5.0,  5.0, -1.0, 0.0, 1.0,
					5.0,  5.0,  1.0, 0.0, 0.0,
					20.0,  5.0,  1.0, 1.0, 0.0,
					20.0,  5.0, -1.0, 1.0, 1.0,
					
					//connect bottom face
					5.0, -5.0, -1.0, 1.0, 1.0,
					20.0, -5.0, -1.0, 0.0, 1.0,
					20.0, -5.0,  1.0, 0.0, 0.0,
					5.0, -5.0,  1.0, 1.0, 0.0,
					
					// right cube Front face
					20.0, -5.0,  5.0, 0.0, 0.0,
					30.0, -5.0,  5.0, 1.0, 0.0,
					30.0,  5.0,  5.0, 1.0, 1.0,
					20.0,  5.0,  5.0, 0.0, 1.0,
					
					// right cube Back face
					20.0, -5.0, -5.0, 1.0, 0.0,
					20.0,  5.0, -5.0, 1.0, 1.0,
					30.0,  5.0, -5.0, 0.0, 1.0,
					30.0, -5.0, -5.0, 0.0, 0.0,
					
					// right cube Top face
					20.0,  5.0, -5.0, 0.0, 1.0,
					20.0,  5.0,  5.0, 0.0, 0.0,
					30.0,  5.0,  5.0, 1.0, 0.0,
					30.0,  5.0, -5.0, 1.0, 1.0,
					
					// right cube Bottom face
					20.0, -5.0, -5.0, 1.0, 1.0,
					30.0, -5.0, -5.0, 0.0, 1.0,
					30.0, -5.0,  5.0, 0.0, 0.0,
					20.0, -5.0,  5.0, 1.0, 0.0,
					
					// right cube Right face
					30.0, -5.0, -5.0, 1.0, 0.0,
					30.0,  5.0, -5.0, 1.0, 1.0,
					30.0,  5.0,  5.0, 0.0, 1.0,
					30.0, -5.0,  5.0, 0.0, 0.0,
					
					// right cube left-front
					20.0, -5.0, 1.0, 0.0, 0.0,
					20.0, -5.0,  5.0, 1.0, 0.0,
					20.0,  5.0,  5.0, 1.0, 1.0,
					20.0,  5.0, 1.0, 0.0, 1.0,
					
					// right cube left-back
					20.0, -5.0, -5.0, 0.0, 0.0,
					20.0, -5.0,  -1.0, 1.0, 0.0,
					20.0,  5.0,  -1.0, 1.0, 1.0,
					20.0,  5.0, -5.0, 0.0, 1.0
				]
			);
			vertexbuffer = context3D.createVertexBuffer( vertexData.length/dataPerVertex, dataPerVertex );
			vertexbuffer.uploadFromVector( vertexData, 0, vertexData.length/dataPerVertex );
			
			var triangles:Vector.<uint> = Vector.<uint>( [ 
				0, 1, 2,      0, 2, 3,    // left Front face
				4, 5, 6,      4, 6, 7,    // left Back face
				8, 9, 10,     8, 10, 11,  // left Top face
				12, 13, 14,   12, 14, 15, // left Bottom face
				16, 17, 18,   16, 18, 19, // left Right face-front
				20, 21, 22,   20, 22, 23, // left Right face-back
				24, 25, 26,   24, 26, 27,  // left Left face
				28, 29, 30,   28, 30, 31, //connect front face
				32, 33, 34,   32, 34, 35, //connect back face
				36, 37, 38,   36, 38, 39, //connect top face
				40, 41, 42,   40, 42, 43, //connect bottom face
				44, 45, 46,   44, 46, 47,    // right Front face
				48, 49, 50,      48, 50, 51,    // right Back face
				52, 53, 54,     52, 54, 55,  // right Top face
				56, 57, 58,   56, 58, 59, // right Bottom face
				60, 61, 62,   60, 62, 63, // right Right face
				64, 65, 66,   64, 66, 67, // right Left face-front
				68, 69, 70,   68, 70, 71  // right Left face-back
			] );
			indexBuffer = context3D.createIndexBuffer( triangles.length );
			indexBuffer.uploadFromVector( triangles, 0, triangles.length );
			
			var bitmap:Bitmap = new TextureBitmap();
			texture = context3D.createTexture(bitmap.bitmapData.width, bitmap.bitmapData.height, Context3DTextureFormat.BGRA, false);
			texture.uploadFromBitmapData(bitmap.bitmapData);
			
			var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
				"m44 op, va0, vc0\n" + // pos to clipspace
				"mov v0, va1" // copy uv
			);
			var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler();
			fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
				"tex ft1, v0, fs0 <2d,linear,nomip>\n" +
				"mov oc, ft1"
			);
			
			program = context3D.createProgram();
			program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
			
			cameraWorldTransform = new Matrix3D();
			cameraWorldTransform.appendTranslation(0, 0, -2);
			viewTransform = new Matrix3D();
			viewTransform = cameraWorldTransform.clone();
			viewTransform.invert();			
			
			cameraLinearVelocity = new Vector3D();
			cameraRotationVelocity = 0;
			
			cameraLinearXAcceleration = 0;
			cameraLinearZAcceleration = 0;
			cameraRotationAcceleration = 0;
			
			projectionTransform = new PerspectiveMatrix3D();
			var aspect:Number = 4/3;
			var zNear:Number = 0.1;
			var zFar:Number = 1000;
			var fov:Number = 45*Math.PI/180;
			projectionTransform.perspectiveFieldOfViewLH(fov, aspect, zNear, zFar);
			
			stage.addEventListener( MouseEvent.MOUSE_MOVE, mouseMoveHandler );
			stage.addEventListener( KeyboardEvent.KEY_DOWN, keyDownEventHandler );   
			stage.addEventListener( KeyboardEvent.KEY_UP, keyUpEventHandler );
			
			addEventListener(Event.ENTER_FRAME, onRender);
		}
		
		private function keyDownEventHandler(e:KeyboardEvent):void
		{
			var keyCode:uint = e.keyCode;
			
			if (keyCode == Keyboard.W ||
				keyCode == Keyboard.S ||
				keyCode == Keyboard.A ||
				keyCode == Keyboard.D) {
				
				if (_currentKeyCodes.indexOf(keyCode) < 0) {
					_currentKeyCodes.push(keyCode);
				}
			}
		}
		
		private function keyUpEventHandler(e:KeyboardEvent):void
		{
			var findingkeyCode:uint = _currentKeyCodes.indexOf(e.keyCode);
			_currentKeyCodes.splice(findingkeyCode, 1);
			cameraRotationAcceleration = 0;
			cameraLinearXAcceleration = 0;
			cameraLinearZAcceleration = 0;
			
			// switching gun
			if (e.keyCode == Keyboard.NUMBER_1) {
				removeChild(_player);
				_player = new DefaultGun();
				_player.scaleX = .3;
				_player.scaleY = .3;
				_player.x = (this.stage.stageWidth - _player.width) * .5;
				_player.y = stage.y + viewHeight - _player.height;
				addChild(_player);
			}
			else if (e.keyCode == Keyboard.NUMBER_2) {
				removeChild(_player);
				_player = new SniperGun();
				_player.scaleX = .5;
				_player.scaleY = .5;
				_player.x = (this.stage.stageWidth - _player.width) * .5;
				_player.y = stage.y + viewHeight - _player.height;
				addChild(_player);
			}
		}
		
		private function mouseMoveHandler(event:MouseEvent):void
		{
			var newX:Number = mouseX;
			var newY:Number = mouseY;
			
			if (_mouseMoveX < 0) {
				_mouseMoveX = newX;
				_mouseMoveY = newY;
			}
			
			_mouseDeltaX = newX - _mouseMoveX;
			_mouseDeltaX *= .3;
			
			_mouseDeltaY = newY - _mouseMoveY;
			_mouseDeltaY *= .3;
			
			// 오른쪽 회전
			if (_mouseDeltaX > 0 && (newX > viewWidth * .5)) {
				cameraRotationAcceleration = ROTATION_ACCELERATION;
			}
			// 오른쪽 회전 중 가운데로 복귀
			else if (_mouseDeltaX < 0 && (newX > viewWidth * .5)) {
				cameraRotationAcceleration = 0;
			}
			// 왼쪽 회전
			else if (_mouseDeltaX < 0 && (newX < viewWidth * .5)) {
				cameraRotationAcceleration = -ROTATION_ACCELERATION;
			}
			// 왼쪽 회전 중 가운데로 복귀
			else if (_mouseDeltaX > 0 && (newX < viewWidth * .5)) {
				cameraRotationAcceleration = 0;
			}
			
			cameraRotationVelocity = calculateUpdatedVelocity(cameraRotationVelocity, cameraRotationAcceleration, MAX_ROTATION_VELOCITY); 
			cameraWorldTransform.appendRotation(cameraRotationVelocity, Vector3D.Y_AXIS, cameraWorldTransform.position);
			
			_mouseMoveX = newX;
			_mouseMoveY = newY;
		}
		
		protected function calculateUpdatedVelocity(curVelocity:Number, curAcceleration:Number, maxVelocity:Number):Number
		{
			var newVelocity:Number;
			
			if (curAcceleration != 0)
			{
				newVelocity = curVelocity + curAcceleration;
				if (newVelocity > maxVelocity)
				{
					newVelocity = maxVelocity;
				}
				else if (newVelocity < -maxVelocity)
				{
					newVelocity = - maxVelocity;
				}
			}
			else
			{
				newVelocity = curVelocity / DAMPING;
			}
			return newVelocity;
		}
		
		protected function updateViewMatrix():void
		{
			for each(var keyCode:uint in _currentKeyCodes) {
				switch (keyCode) {
					case Keyboard.W :
						cameraLinearZAcceleration = LINEAR_ACCELERATION;
						break;
					case Keyboard.S :
						cameraLinearZAcceleration = -LINEAR_ACCELERATION;
						break;
					case Keyboard.A :
						cameraLinearXAcceleration = -LINEAR_ACCELERATION;
						break;
					case Keyboard.D :
						cameraLinearXAcceleration = LINEAR_ACCELERATION;
						break;
					default :
						break;
				}
			}
			
			cameraLinearVelocity.x = calculateUpdatedVelocity(cameraLinearVelocity.x, cameraLinearXAcceleration, MAX_FORWARD_VELOCITY);
			cameraLinearVelocity.z = calculateUpdatedVelocity(cameraLinearVelocity.z, cameraLinearZAcceleration, MAX_FORWARD_VELOCITY);
			
			cameraWorldTransform.position = cameraWorldTransform.transformVector(cameraLinearVelocity);			
			cameraWorldTransform.appendRotation(cameraRotationVelocity, Vector3D.Y_AXIS, cameraWorldTransform.position);			
			
			viewTransform.copyFrom(cameraWorldTransform);
			viewTransform.invert();
		}
		
		protected function onRender(e:Event):void
		{
			if ( !context3D ) 
				return;
			
			context3D.clear ( 1, 1, 1, 1 );
			
			// vertex position to attribute register 0
			context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
			// uv coordinates to attribute register 1
			context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
			// assign texture to texture sampler 0
			context3D.setTextureAt(0, texture);			
			// assign shader program
			context3D.setProgram(program);
			
			updateViewMatrix();
			
			var m:Matrix3D = new Matrix3D();
			m.appendTranslation(0, 0, 2);
			m.append(viewTransform);
			m.append(projectionTransform);
			
			context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true);
			
			context3D.drawTriangles(indexBuffer);
			
			context3D.present();
		}
	}
}